home *** CD-ROM | disk | FTP | other *** search
/ Nebula 1 / Nebula One.iso / Utilities / Unix / cvs-960311 / bin / rcs2log < prev    next >
Text File  |  1996-03-10  |  15KB  |  593 lines

  1. #! /bin/sh
  2.  
  3. # RCS to ChangeLog generator
  4.  
  5. # Generate a change log prefix from RCS files and the ChangeLog (if any).
  6. # Output the new prefix to standard output.
  7. # You can edit this prefix by hand, and then prepend it to ChangeLog.
  8.  
  9. # Ignore log entries that start with `#'.
  10. # Clump together log entries that start with `{topic} ',
  11. # where `topic' contains neither white space nor `}'.
  12.  
  13. # Author: Paul Eggert <eggert@twinsun.com>
  14.  
  15. # $Id: rcs2log.sh,v 1.1.1.1 1996/01/20 15:40:57 bbum Exp $
  16.  
  17. # Copyright 1992, 1993, 1994, 1995 Free Software Foundation, Inc.
  18.  
  19. # This program is free software; you can redistribute it and/or modify
  20. # it under the terms of the GNU General Public License as published by
  21. # the Free Software Foundation; either version 2, or (at your option)
  22. # any later version.
  23. # This program is distributed in the hope that it will be useful,
  24. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  25. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  26. # GNU General Public License for more details.
  27. # You should have received a copy of the GNU General Public License
  28. # along with this program; see the file COPYING.  If not, write to
  29. # the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
  30.  
  31. tab='    '
  32. nl='
  33. '
  34.  
  35. # Parse options.
  36.  
  37. # defaults
  38. : ${AWK=awk}
  39. : ${TMPDIR=/tmp}
  40. hostname= # name of local host (if empty, will deduce it later)
  41. indent=8 # indent of log line
  42. length=79 # suggested max width of log line
  43. logins= # login names for people we know fullnames and mailaddrs of
  44. loginFullnameMailaddrs= # login<tab>fullname<tab>mailaddr triplets
  45. recursive= # t if we want recursive rlog
  46. rlog_options= # options to pass to rlog
  47. tabwidth=8 # width of horizontal tab
  48.  
  49. while :
  50. do
  51.     case $1 in
  52.     -i)    indent=${2?}; shift;;
  53.     -h)    hostname=${2?}; shift;;
  54.     -l)    length=${2?}; shift;;
  55.     -[nu])    # -n is obsolescent; it is replaced by -u.
  56.         case $1 in
  57.         -n)    case ${2?}${3?}${4?} in
  58.             *"$tab"* | *"$nl"*)
  59.                 echo >&2 "$0: -n '$2' '$3' '$4': tabs, newlines not allowed"
  60.                 exit 1
  61.             esac
  62.             loginFullnameMailaddrs=$loginFullnameMailaddrs$nl$2$tab$3$tab$4
  63.             shift; shift; shift;;
  64.         -u)
  65.             # If $2 is not tab-separated, use colon for separator.
  66.             case ${2?} in
  67.             *"$nl"*)
  68.                 echo >&2 "$0: -u '$2': newlines not allowed"
  69.                 exit 1;;
  70.             *"$tab"*)
  71.                 t=$tab;;
  72.             *)
  73.                 t=:
  74.             esac
  75.             case $2 in
  76.             *"$t"*"$t"*"$t"*)
  77.                 echo >&2 "$0: -u '$2': too many fields"
  78.                 exit 1;;
  79.             *"$t"*"$t"*)
  80.                 ;;
  81.             *)
  82.                 echo >&2 "$0: -u '$2': not enough fields"
  83.                 exit 1
  84.             esac
  85.             loginFullnameMailaddrs=$loginFullnameMailaddrs$nl$2
  86.             shift
  87.         esac
  88.         logins=$logins$nl$login
  89.         ;;
  90.     -r)    rlog_options=$rlog_options$nl${2?}; shift;;
  91.     -R)    recursive=t;;
  92.     -t)    tabwidth=${2?}; shift;;
  93.     -*)    echo >&2 "$0: usage: $0 [options] [file ...]
  94. Options:
  95.     [-h hostname] [-i indent] [-l length] [-R] [-r rlog_option]
  96.     [-t tabwidth] [-u 'login<TAB>fullname<TAB>mailaddr']..."
  97.         exit 1;;
  98.     *)    break
  99.     esac
  100.     shift
  101. done
  102.  
  103. month_data='
  104.     m[0]="Jan"; m[1]="Feb"; m[2]="Mar"
  105.     m[3]="Apr"; m[4]="May"; m[5]="Jun"
  106.     m[6]="Jul"; m[7]="Aug"; m[8]="Sep"
  107.     m[9]="Oct"; m[10]="Nov"; m[11]="Dec"
  108.  
  109.     # days in non-leap year thus far, indexed by month (0-12)
  110.     mo[0]=0; mo[1]=31; mo[2]=59; mo[3]=90
  111.     mo[4]=120; mo[5]=151; mo[6]=181; mo[7]=212
  112.     mo[8]=243; mo[9]=273; mo[10]=304; mo[11]=334
  113.     mo[12]=365
  114. '
  115.  
  116.  
  117. # Put rlog output into $rlogout.
  118.  
  119. # If no rlog options are given,
  120. # log the revisions checked in since the first ChangeLog entry.
  121. case $rlog_options in
  122. '')
  123.     date=1970
  124.     if test -s ChangeLog
  125.     then
  126.         # Add 1 to seconds to avoid duplicating most recent log.
  127.         e='
  128.             /^... ... [ 0-9][0-9] [ 0-9][0-9]:[0-9][0-9]:[0-9][0-9] [0-9]+ /{
  129.                 '"$month_data"'
  130.                 year = $5
  131.                 for (i=0; i<=11; i++) if (m[i] == $2) break
  132.                 dd = $3
  133.                 hh = substr($0,12,2)
  134.                 mm = substr($0,15,2)
  135.                 ss = substr($0,18,2)
  136.                 ss++
  137.                 if (ss == 60) {
  138.                     ss = 0
  139.                     mm++
  140.                     if (mm == 60) {
  141.                         mm = 0
  142.                         hh++
  143.                         if (hh == 24) {
  144.                             hh = 0
  145.                             dd++
  146.                             monthdays = mo[i+1] - mo[i]
  147.                             if (i == 1 && year%4 == 0 && (year%100 != 0 || year%400 == 0)) monthdays++
  148.                             if (dd == monthdays + 1) {
  149.                                 dd = 1
  150.                                 i++
  151.                                 if (i == 12) {
  152.                                     i = 0
  153.                                     year++
  154.                                 }
  155.                             }
  156.                         }
  157.                     }
  158.                 }
  159.                 # Output comma instead of space to avoid CVS 1.5 bug.
  160.                 printf "%d/%02d/%02d,%02d:%02d:%02d\n", year,i+1,dd,hh,mm,ss
  161.                 exit
  162.             }
  163.         '
  164.         d=`$AWK "$e" <ChangeLog` || exit
  165.         case $d in
  166.         ?*) date=$d
  167.         esac
  168.     fi
  169.     datearg="-d>$date"
  170. esac
  171.  
  172. # If CVS is in use, examine its repository, not the normal RCS files.
  173. if test ! -f CVS/Repository
  174. then
  175.     rlog=rlog
  176.     repository=
  177. else
  178.     rlog='cvs log'
  179.     repository=`sed 1q <CVS/Repository` || exit
  180.     test ! -f CVS/Root || CVSROOT=`cat <CVS/Root` || exit
  181.     case $CVSROOT in
  182.     *:/*)
  183.         # remote repository
  184.         ;;
  185.     *)
  186.         # local repository
  187.         case $repository in
  188.         /*) ;;
  189.         *) repository=${CVSROOT?}/$repository
  190.         esac
  191.         if test ! -d "$repository"
  192.         then
  193.             echo >&2 "$0: $repository: bad repository (see CVS/Repository)"
  194.             exit 1
  195.         fi
  196.     esac
  197. fi
  198.  
  199. # With no arguments, examine all files under the RCS directory.
  200. case $# in
  201. 0)
  202.     case $repository in
  203.     '')
  204.         oldIFS=$IFS
  205.         IFS=$nl
  206.         case $recursive in
  207.         t)
  208.             RCSdirs=`find . -name RCS -type d -print`
  209.             filesFromRCSfiles='s|,v$||; s|/RCS/|/|; s|^\./||'
  210.             files=`
  211.                 {
  212.                     case $RCSdirs in
  213.                     ?*) find $RCSdirs -type f -print
  214.                     esac
  215.                     find . -name '*,v' -print
  216.                 } |
  217.                 sort -u |
  218.                 sed "$filesFromRCSfiles"
  219.             `;;
  220.         *)
  221.             files=
  222.             for file in RCS/.* RCS/* .*,v *,v
  223.             do
  224.                 case $file in
  225.                 RCS/. | RCS/..) continue;;
  226.                 RCS/.\* | RCS/\* | .\*,v | \*,v) test -f "$file" || continue
  227.                 esac
  228.                 files=$files$nl$file
  229.             done
  230.             case $files in
  231.             '') exit 0
  232.             esac
  233.         esac
  234.         set x $files
  235.         shift
  236.         IFS=$oldIFS
  237.     esac
  238. esac
  239.  
  240. llogout=$TMPDIR/rcs2log$$l
  241. rlogout=$TMPDIR/rcs2log$$r
  242. trap exit 1 2 13 15
  243. trap "rm -f $llogout $rlogout; exit 1" 0
  244.  
  245. case $rlog_options in
  246. ?*) $rlog $rlog_options ${1+"$@"} >$rlogout;;
  247. '') $rlog "$datearg" ${1+"$@"} >$rlogout
  248. esac || exit
  249.  
  250.  
  251. # Get the full name of each author the logs mention, and set initialize_fullname
  252. # to awk code that initializes the `fullname' awk associative array.
  253. # Warning: foreign authors (i.e. not known in the passwd file) are mishandled;
  254. # you have to fix the resulting output by hand.
  255.  
  256. initialize_fullname=
  257. initialize_mailaddr=
  258.  
  259. case $loginFullnameMailaddrs in
  260. ?*)
  261.     case $loginFullnameMailaddrs in
  262.     *\"* | *\\*)
  263.         sed 's/["\\]/\\&/g' >$llogout <<EOF || exit
  264. $loginFullnameMailaddrs
  265. EOF
  266.         loginFullnameMailaddrs=`cat $llogout`
  267.     esac
  268.  
  269.     oldIFS=$IFS
  270.     IFS=$nl
  271.     for loginFullnameMailaddr in $loginFullnameMailaddrs
  272.     do
  273.         case $loginFullnameMailaddr in
  274.         *"$tab"*) IFS=$tab;;
  275.         *) IFS=:
  276.         esac
  277.         set x $loginFullnameMailaddr
  278.         login=$2
  279.         fullname=$3
  280.         mailaddr=$4
  281.         initialize_fullname="$initialize_fullname
  282.             fullname[\"$login\"] = \"$fullname\""
  283.         initialize_mailaddr="$initialize_mailaddr
  284.             mailaddr[\"$login\"] = \"$mailaddr\""
  285.     done
  286.     IFS=$oldIFS
  287. esac
  288.  
  289. case $llogout in
  290. ?*) sort -u -o $llogout <<EOF || exit
  291. $logins
  292. EOF
  293. esac
  294. output_authors='/^date: / {
  295.     if ($2 ~ /^[0-9]*[-\/][0-9][0-9][-\/][0-9][0-9]$/ && $3 ~ /^[0-9][0-9]:[0-9][0-9]:[0-9][0-9][-+0-9:]*;$/ && $4 == "author:" && $5 ~ /^[^;]*;$/) {
  296.         print substr($5, 1, length($5)-1)
  297.     }
  298. }'
  299. authors=`
  300.     $AWK "$output_authors" <$rlogout |
  301.     case $llogout in
  302.     '') sort -u;;
  303.     ?*) sort -u | comm -23 - $llogout
  304.     esac
  305. `
  306. case $authors in
  307. ?*)
  308.     cat >$llogout <<EOF || exit
  309. $authors
  310. EOF
  311.     initialize_author_script='s/["\\]/\\&/g; s/.*/author[\"&\"] = 1/'
  312.     initialize_author=`sed -e "$initialize_author_script" <$llogout`
  313.     awkscript='
  314.         BEGIN {
  315.             alphabet = "abcdefghijklmnopqrstuvwxyz"
  316.             ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
  317.             '"$initialize_author"'
  318.         }
  319.         {
  320.             if (author[$1]) {
  321.                 fullname = $5
  322.                 if (fullname ~ /[0-9]+-[^(]*\([0-9]+\)$/) {
  323.                     # Remove the junk from fullnames like "0000-Admin(0000)".
  324.                     fullname = substr(fullname, index(fullname, "-") + 1)
  325.                     fullname = substr(fullname, 1, index(fullname, "(") - 1)
  326.                 }
  327.                 if (fullname ~ /,[^ ]/) {
  328.                     # Some sites put comma-separated junk after the fullname.
  329.                     # Remove it, but leave "Bill Gates, Jr" alone.
  330.                     fullname = substr(fullname, 1, index(fullname, ",") - 1)
  331.                 }
  332.                 abbr = index(fullname, "&")
  333.                 if (abbr) {
  334.                     a = substr($1, 1, 1)
  335.                     A = a
  336.                     i = index(alphabet, a)
  337.                     if (i) A = substr(ALPHABET, i, 1)
  338.                     fullname = substr(fullname, 1, abbr-1) A substr($1, 2) substr(fullname, abbr+1)
  339.                 }
  340.  
  341.                 # Quote quotes and backslashes properly in full names.
  342.                 # Do not use gsub; traditional awk lacks it.
  343.                 quoted = ""
  344.                 rest = fullname
  345.                 for (;;) {
  346.                     p = index(rest, "\\")
  347.                     q = index(rest, "\"")
  348.                     if (p) {
  349.                         if (q && q<p) p = q
  350.                     } else {
  351.                         if (!q) break
  352.                         p = q
  353.                     }
  354.                     quoted = quoted substr(rest, 1, p-1) "\\" substr(rest, p, 1)
  355.                     rest = substr(rest, p+1)
  356.                 }
  357.  
  358.                 printf "fullname[\"%s\"] = \"%s%s\"\n", $1, quoted, rest
  359.                 author[$1] = 0
  360.             }
  361.         }
  362.     '
  363.  
  364.     initialize_fullname=`
  365.         (cat /etc/passwd; ypmatch $authors passwd) 2>/dev/null |
  366.         $AWK -F: "$awkscript"
  367.     `$initialize_fullname
  368. esac
  369.  
  370.  
  371. # Function to print a single log line.
  372. # We don't use awk functions, to stay compatible with old awk versions.
  373. # `Log' is the log message (with \n replaced by \r).
  374. # `files' contains the affected files.
  375. printlogline='{
  376.  
  377.     # Following the GNU coding standards, rewrite
  378.     #    * file: (function): comment
  379.     # to
  380.     #    * file (function): comment
  381.     if (Log ~ /^\([^)]*\): /) {
  382.         i = index(Log, ")")
  383.         files = files " " substr(Log, 1, i)
  384.         Log = substr(Log, i+3)
  385.     }
  386.  
  387.     # If "label: comment" is too long, break the line after the ":".
  388.     sep = " "
  389.     if ('"$length"' <= '"$indent"' + 1 + length(files) + index(Log, CR)) sep = "\n" indent_string
  390.  
  391.     # Print the label.
  392.     printf "%s*%s:", indent_string, files
  393.  
  394.     # Print each line of the log, transliterating \r to \n.
  395.     while ((i = index(Log, CR)) != 0) {
  396.         logline = substr(Log, 1, i-1)
  397.         if (logline ~ /[^'"$tab"' ]/) {
  398.             printf "%s%s\n", sep, logline
  399.         } else {
  400.             print ""
  401.         }
  402.         sep = indent_string
  403.         Log = substr(Log, i+1)
  404.     }
  405. }'
  406.  
  407. case $hostname in
  408. '')
  409.     hostname=`(
  410.         hostname || uname -n || uuname -l || cat /etc/whoami
  411.     ) 2>/dev/null` || {
  412.         echo >&2 "$0: cannot deduce hostname"
  413.         exit 1
  414.     }
  415. esac
  416.  
  417.  
  418. # Process the rlog output, generating ChangeLog style entries.
  419.  
  420. # First, reformat the rlog output so that each line contains one log entry.
  421. # Transliterate \n to \r so that multiline entries fit on a single line.
  422. # Discard irrelevant rlog output.
  423. $AWK <$rlogout '
  424.     BEGIN { repository = "'"$repository"'" }
  425.     /^RCS file:/ {
  426.         if (repository != "") {
  427.             filename = $3
  428.             if (substr(filename, 1, length(repository) + 1) == repository "/") {
  429.                 filename = substr(filename, length(repository) + 2)
  430.             }
  431.             if (filename ~ /,v$/) {
  432.                 filename = substr(filename, 1, length(filename) - 2)
  433.             }
  434.         }
  435.     }
  436.     /^Working file:/ { if (repository == "") filename = $3 }
  437.     /^date: /, /^(-----------*|===========*)$/ {
  438.         if ($0 ~ /^branches: /) { next }
  439.         if ($0 ~ /^date: [0-9][- +\/0-9:]*;/) {
  440.             date = $2
  441.             if (date ~ /-/) {
  442.                 # An ISO format date.  Replace all "-"s with "/"s.
  443.                 newdate = ""
  444.                 while ((i = index(date, "-")) != 0) {
  445.                     newdate = newdate substr(date, 1, i-1) "/"
  446.                     date = substr(date, i+1)
  447.                 }
  448.                 date = newdate date
  449.             }
  450.             # Ignore any time zone; ChangeLog has no room for it.
  451.             time = substr($3, 1, 8)
  452.             author = substr($5, 1, length($5)-1)
  453.             printf "%s %s %s %s %c", filename, date, time, author, 13
  454.             next
  455.         }
  456.         if ($0 ~ /^(-----------*|===========*)$/) { print ""; next }
  457.         printf "%s%c", $0, 13
  458.     }
  459. ' |
  460.  
  461. # Now each line is of the form
  462. # FILENAME YYYY/MM/DD HH:MM:SS AUTHOR \rLOG
  463. #    where \r stands for a carriage return,
  464. #    and each line of the log is terminated by \r instead of \n.
  465. # Sort the log entries, first by date+time (in reverse order),
  466. # then by author, then by log entry, and finally by file name (just in case).
  467. sort +1 -3r +3 +0 |
  468.  
  469. # Finally, reformat the sorted log entries.
  470. $AWK '
  471.     BEGIN {
  472.         # Some awk variants do not understand "\r" or "\013", so we have to
  473.         # put a carriage return directly in the file.
  474.         CR="" # <-- There is a single CR between the " chars here.
  475.  
  476.         # Initialize the fullname and mailaddr associative arrays.
  477.         '"$initialize_fullname"'
  478.         '"$initialize_mailaddr"'
  479.  
  480.         # Initialize indent string.
  481.         indent_string = ""
  482.         i = '"$indent"'
  483.         if (0 < '"$tabwidth"')
  484.             for (;  '"$tabwidth"' <= i;  i -= '"$tabwidth"')
  485.                 indent_string = indent_string "\t"
  486.         while (1 <= i--)
  487.             indent_string = indent_string " "
  488.  
  489.         # Set up date conversion tables.
  490.         # RCS uses a nice, clean, sortable format,
  491.         # but ChangeLog wants the traditional, ugly ctime format.
  492.  
  493.         # January 1, 0 AD (Gregorian) was Saturday = 6
  494.         EPOCH_WEEKDAY = 6
  495.         # Of course, there was no 0 AD, but the algorithm works anyway.
  496.  
  497.         w[0]="Sun"; w[1]="Mon"; w[2]="Tue"; w[3]="Wed"
  498.         w[4]="Thu"; w[5]="Fri"; w[6]="Sat"
  499.  
  500.         '"$month_data"'
  501.     }
  502.  
  503.     {
  504.         newlog = substr($0, 1 + index($0, CR))
  505.  
  506.         # Ignore log entries prefixed by "#".
  507.         if (newlog ~ /^#/) { next }
  508.  
  509.         if (Log != newlog || date != $2 || author != $4) {
  510.  
  511.             # The previous log and this log differ.
  512.  
  513.             # Print the old log.
  514.             if (date != "") '"$printlogline"'
  515.  
  516.             # Logs that begin with "{clumpname} " should be grouped together,
  517.             # and the clumpname should be removed.
  518.             # Extract the new clumpname from the log header,
  519.             # and use it to decide whether to output a blank line.
  520.             newclumpname = ""
  521.             sep = "\n"
  522.             if (date == "") sep = ""
  523.             if (newlog ~ /^\{[^'"$tab"' }]*}['"$tab"' ]/) {
  524.                 i = index(newlog, "}")
  525.                 newclumpname = substr(newlog, 1, i)
  526.                 while (substr(newlog, i+1) ~ /^['"$tab"' ]/) i++
  527.                 newlog = substr(newlog, i+1)
  528.                 if (clumpname == newclumpname) sep = ""
  529.             }
  530.             printf sep
  531.             clumpname = newclumpname
  532.  
  533.             # Get ready for the next log.
  534.             Log = newlog
  535.             if (files != "")
  536.                 for (i in filesknown)
  537.                     filesknown[i] = 0
  538.             files = ""
  539.         }
  540.         if (date != $2  ||  author != $4) {
  541.             # The previous date+author and this date+author differ.
  542.             # Print the new one.
  543.             date = $2
  544.             author = $4
  545.  
  546.             # Convert nice RCS date like "1992/01/03 00:03:44"
  547.             # into ugly ctime date like "Fri Jan  3 00:03:44 1992".
  548.             # Calculate day of week from Gregorian calendar.
  549.             i = index($2, "/")
  550.             year = substr($2, 1, i-1) + 0
  551.             monthday = substr($2, i+1)
  552.             i = index(monthday, "/")
  553.             month = substr(monthday, 1, i-1) + 0
  554.             day = substr(monthday, i+1) + 0
  555.             leap = 0
  556.             if (2 < month && year%4 == 0 && (year%100 != 0 || year%400 == 0)) leap = 1
  557.             days_since_Sunday_before_epoch = EPOCH_WEEKDAY + year * 365 + int((year + 3) / 4) - int((year + 99) / 100) + int((year + 399) / 400) + mo[month-1] + leap + day - 1
  558.  
  559.             # Print "date  fullname  (email address)".
  560.             # Get fullname and email address from associative arrays;
  561.             # default to author and author@hostname if not in arrays.
  562.             if (fullname[author])
  563.                 auth = fullname[author]
  564.             else
  565.                 auth = author
  566.             printf "%s %s %2d %s %d  %s  ", w[days_since_Sunday_before_epoch%7], m[month-1], day, $3, year, auth
  567.             if (mailaddr[author])
  568.                 printf "<%s>\n\n", mailaddr[author]
  569.             else
  570.                 printf "<%s@%s>\n\n", author, "'"$hostname"'"
  571.         }
  572.         if (! filesknown[$1]) {
  573.             filesknown[$1] = 1
  574.             if (files == "") files = " " $1
  575.             else files = files ", " $1
  576.         }
  577.     }
  578.     END {
  579.         # Print the last log.
  580.         if (date != "") {
  581.             '"$printlogline"'
  582.             printf "\n"
  583.         }
  584.     }
  585. ' &&
  586.  
  587.  
  588. # Exit successfully.
  589.  
  590. exec rm -f $llogout $rlogout
  591.